package view_service

import (
	"encoding/json"
	"fmt"
	"os"
	"peilian-api/app/global/common"
	"peilian-api/app/global/databases/elastic"
	"peilian-api/app/global/databases/tables"
	"peilian-api/app/global/log"
	"peilian-api/app/global/variable"
	"peilian-api/app/model"
	"peilian-api/app/services/train_service"
	"peilian-api/utils/sdk"
	"peilian-api/utils/tools"
	"time"

	"github.com/gin-gonic/gin"
)

type LabelResp struct {
	Label   string  `json:"label"`
	Count   int     `json:"count"`
	Percent float64 `json:"percent"`
}

type QueryConfig struct {
	DocSize int
}

type Query struct {
	LoginUserType string     `form:"-" json:"login_user_type"` // 当前用户权限
	Type          string     `form:"type" json:"type" validate:"required"`
	Option        int        `form:"option" validate:"omitempty"` //学院或者专业的ID
	Grade         string     `form:"grade" validate:"omitempty"`  //年级的名称
	StartTime     *time.Time `form:"start_time" time_format:"2006-01-02 15:04:05" validate:"required"`
	EndTime       *time.Time `form:"end_time" time_format:"2006-01-02 15:04:05" validate:"omitempty"`

	CareerId     int    `json:"career" form:"career" validate:"omitempty"`
	SchoolId     int    `json:"school" form:"school" validate:"omitempty"`
	AcademyId    int    `json:"academy" form:"academy" validate:"omitempty"`
	ProfessionId int    `json:"profession" form:"profession" validate:"omitempty"`
	Education    string `json:"education" form:"education" validate:"omitempty"`

	UserType   string             `form:"user_type"`
	UserId     int                `form:"user_id"`
	AgentId    int                `form:"-"`
	Constitute *tables.Constitute `form:"-"`
	MemberInfo []model.CountInfo  `form:"-"`
	CareerInfo []model.CountInfo  `form:"-"`
	CareerAll  []string           `form:"-"`
	CareerMap  map[uint]string    `form:"-"`

	esResp *EsAggregationRes
}

func (q *Query) SetTopConstitute(ctx *gin.Context) error {
	if q.Constitute != nil {
		return nil
	}
	if q.Option == 0 {
		var agent tables.Agent
		if err := variable.DB.MySQL.Preload("FirstConstitute.BelongTo").
			First(&agent, q.AgentId).Error; err != nil {
			tools.Resp400(ctx, "数据库查询错误: "+err.Error())
			return err
		}
		q.Constitute = &agent.FirstConstitute
	} else {
		if err := variable.DB.MySQL.Preload("BelongTo").
			First(&q.Constitute, q.Option).Error; err != nil {
			tools.Resp400(ctx, "查询构成对象时报错: "+err.Error())
			return err
		}
	}
	return nil
}

func (q *Query) EsQuery() *elastic.Query {
	// 兼容个人能力查询
	query := elastic.NewBoolQuery()
	var start, end int64
	if q.StartTime == nil {
		start = 0
	} else {
		start = q.StartTime.Unix()
	}
	if q.EndTime == nil {
		end = time.Now().Add(time.Hour).Unix()
	} else {
		end = q.EndTime.Unix()
	}
	query = query.Must(elastic.NewRangeQuery("train_at", start, end))

	if q.Constitute != nil && q.Constitute.Name != "" {
		query.Must(elastic.NewTermQuery(q.Constitute.Type, q.Constitute.Name))
	}
	if q.SchoolId != 0 {
		var school string
		if err := variable.DB.MySQL.Model(&tables.Constitute{}).Select("Name").
			Where("id=?", q.SchoolId).Scan(&school).Error; err == nil {
			if school != "游客" {
				query = query.Must(elastic.NewTermQuery("school", school))
			} else {
				query = query.Must(elastic.NewTermQuery("school", ""))
			}
		}
	}
	if q.UserType != "" && q.UserType != "0" {
		utype := tables.StafMapArr[q.UserType]
		query = query.Must(elastic.NewTermQuery("user_type", utype))
	}
	if q.UserId != 0 {
		query = query.Must(elastic.NewTermQuery("user_id", q.UserId))
		return query
	}
	if q.AgentId != 0 {
		query = query.Must(elastic.NewTermQuery("agent_id", q.AgentId))
	}

	if q.LoginUserType != tables.UserTypeSadmin {
		constitute := q.Constitute
		for {
			if constitute == nil {
				break
			}
			if constitute.BelongTo == nil {
				break
			}
			query = query.Must(elastic.NewTermQuery(constitute.BelongTo.Type, constitute.BelongTo.Name))
			var c tables.Constitute
			if err := variable.DB.MySQL.Preload("BelongTo").
				First(&c, constitute.BelongToID).Error; err != nil {
				return nil
			}
			constitute = &c
		}
	}
	if q.Grade != "0" && q.Grade != "" {
		query = query.Must(elastic.NewTermQuery("grade", q.Grade))
	}
	if q.CareerId != 0 {
		query = query.Must(elastic.NewTermQuery("career_id", q.CareerId))
	}

	if q.AcademyId != 0 {
		var academy string
		if err := variable.DB.MySQL.Model(&tables.Constitute{}).Select("Name").
			Where("id=?", q.AcademyId).Scan(&academy).Error; err == nil {
			query = query.Must(elastic.NewTermQuery("academy", academy))
		}
	}
	if q.ProfessionId != 0 {
		var profession string
		if err := variable.DB.MySQL.Model(&tables.Constitute{}).Select("Name").
			Where("id=?", q.ProfessionId).Scan(&profession).Error; err == nil {
			query = query.Must(elastic.NewTermQuery("profession", profession))
		}
	}
	if q.Education != "" {
		query = query.Must(elastic.NewTermQuery("education", q.Education))
	}
	return query
}

// 能力总结
func (q *Query) EsSearchLabel(labelType string, exportflag bool) ([]*LabelResp, error) {
	query := q.EsQuery()
	aggTotalUserCount := elastic.NewCardinalityAggregation("user_id")
	aggSessionIdLi := elastic.NewTermsAggregation("session_id")
	res, err := elastic.NewEsClient().Size(0).Query(query).Aggregation("total_user_count", aggTotalUserCount).
		Aggregation("session_id_li", aggSessionIdLi).Search()
	if err != nil {
		return nil, err
	}
	q.SetResult(res)
	totalUserCount := q.esResp.Aggregation.TotalUserCount.Value.(float64)
	sessionIdLi := make([]int, 0)
	for _, sessionId := range q.esResp.Aggregation.SessionIdLi.Buckets {
		sessionIdLi = append(sessionIdLi, int(sessionId.Key.(float64)))
	}

	labelQuery := elastic.NewBoolQuery().Must(elastic.NewTermQuery("type", labelType))
	if q.UserId != 0 {
		labelQuery = labelQuery.Must(elastic.NewTermQuery("user_id", q.UserId))
	}
	labelQuery = labelQuery.Must(elastic.NewTermsQuery("session_id", sessionIdLi))

	var sizeTerms uint
	switch labelType {
	case "experience:pos", "experience:neg", "pro:pos", "pro:neg", "gen:pos", "gen:neg":
		sizeTerms = 10
	case "tool", "personality":
		sizeTerms = 30
	default:
		sizeTerms = 2147483647
	}
	if exportflag {
		sizeTerms = 2147483647
	}

	var aggLabel *elastic.Aggregation
	labelResp := make([]*LabelResp, 0)
	// 如果是个人总结
	if q.UserId != 0 {
		aggLabel = elastic.NewTermsAggregation("label").SizeTerms(sizeTerms)
		labelRes, err := elastic.NewEsClient().Size(0).Query(labelQuery).Aggregation("label_aggs", aggLabel).
			Search(variable.Config.Elasticsearch.LabelIndex)
		if err != nil {
			return nil, err
		}
		q.SetResult(labelRes)
		if q.esResp != nil && q.esResp.Aggregation != nil && q.esResp.Aggregation.LabelAggs != nil && q.esResp.Aggregation.LabelAggs.Buckets != nil {
			for _, l := range q.esResp.Aggregation.LabelAggs.Buckets {
				if len(sessionIdLi) == 0 {
					labelResp = append(labelResp, &LabelResp{
						Label:   l.Key.(string),
						Count:   0,
						Percent: 0,
					})
					continue
				}
				labelResp = append(labelResp, &LabelResp{
					Label:   l.Key.(string),
					Count:   int(l.DocCount),
					Percent: float64(l.DocCount) / float64(len(sessionIdLi)),
				})
			}
		}
	} else {
		aggLabel = elastic.NewTermsAggregation("label").SizeTerms(sizeTerms).
			OrderTerms(map[string]string{"distinct_user_count": "desc"}).
			SubAggregation("distinct_user_count", elastic.NewCardinalityAggregation("user_id"))
		labelRes, err := elastic.NewEsClient().Size(0).Query(labelQuery).Aggregation("label_aggs", aggLabel).
			Search(variable.Config.Elasticsearch.LabelIndex)
		if err != nil {
			return nil, err
		}
		q.SetResult(labelRes)
		if q.esResp != nil && q.esResp.Aggregation != nil && q.esResp.Aggregation.LabelAggs != nil && q.esResp.Aggregation.LabelAggs.Buckets != nil {
			for _, l := range q.esResp.Aggregation.LabelAggs.Buckets {
				labelResp = append(labelResp, &LabelResp{
					Label:   l.Key.(string),
					Count:   l.DistinctUserCount.Value,
					Percent: float64(l.DistinctUserCount.Value) / totalUserCount,
				})
			}
		}
	}
	if labelType == "career_recommend" {
		aggLabel = elastic.NewCardinalityAggregation("user_id")
		labelRes, err := elastic.NewEsClient().Size(0).Query(labelQuery).Aggregation("distinct_user_count", aggLabel).
			Search(variable.Config.Elasticsearch.LabelIndex)
		if err != nil {
			return nil, err
		}
		q.SetResult(labelRes)
		RecommendCnt := q.esResp.Aggregation.DistinctUserCount.Value.(float64)
		noRecommendCnt := totalUserCount - RecommendCnt
		var percent float64
		if totalUserCount <= 0 {
			percent = 1
		} else {
			percent = noRecommendCnt / totalUserCount
		}

		labelResp = append(labelResp, &LabelResp{
			Label:   "暂无推荐",
			Count:   int(noRecommendCnt),
			Percent: percent,
		})
	}
	return labelResp, nil
}

func (q *Query) EsSearchState() (*InterviewState, error) {
	query := q.EsQuery()
	res, err := elastic.NewEsClient().Size(0).Query(query).
		Aggregation("expressive_avg", elastic.NewAvgAggregation("express_score")).
		Aggregation("emphasis_avg", elastic.NewAvgAggregation("emphasis_score")).
		Aggregation("logic_avg", elastic.NewAvgAggregation("logic_score")).Search()
	if err != nil {
		return nil, err
	}
	q.SetResult(res)
	interviewState := &InterviewState{}
	if q.esResp != nil {
		if q.esResp.Aggregation != nil {
			if q.esResp.Aggregation.ExpressAvg != nil {
				if q.esResp.Aggregation.ExpressAvg.Value != nil {
					interviewState.ExpressiveAvg = q.esResp.Aggregation.ExpressAvg.Value.(float64)
				}
			}
			if q.esResp.Aggregation.EmphasisAvg != nil {
				if q.esResp.Aggregation.EmphasisAvg.Value != nil {
					interviewState.EmphasisAvg = q.esResp.Aggregation.EmphasisAvg.Value.(float64)
				}
			}
			if q.esResp.Aggregation.LogicAvg != nil {
				if q.esResp.Aggregation.LogicAvg.Value != nil {
					interviewState.LogicAvg = q.esResp.Aggregation.LogicAvg.Value.(float64)
				}
			}
		}
	}

	return interviewState, nil
}

func (q *Query) SetResult(res []byte) {
	var esResp EsAggregationRes
	_ = json.Unmarshal(res, &esResp)
	q.esResp = &esResp
}

func (q *Query) EsSearchCount() error {
	query := q.EsQuery()
	careerAgg := elastic.NewTermsAggregation("career_name").SubAggregation("avg_score", elastic.NewAvgAggregation("score"))
	var constituteType string
	switch q.Constitute.Type {
	case "school":
		constituteType = "academy"
	case "academy":
		constituteType = "profession"
	default:
		constituteType = "academy"
	}
	constituteAgg := elastic.NewTermsAggregation(constituteType).SubAggregation("avg_score", elastic.NewAvgAggregation("score"))
	res, err := elastic.NewEsClient().Size(0).Query(query).Aggregation("career_condition", careerAgg).
		Aggregation("constitute_condition", constituteAgg).Search()
	if err != nil {
		return err
	}
	log.Logger.Warn(fmt.Sprintf("debug 3:%s", res))
	q.SetResult(res)
	return nil
}

func (q *Query) EsSearchTotal() error {
	query := q.EsQuery()
	agg := elastic.NewRangeAggregation("train_at")
	inDuraDate := q.InDuraDate()
	if q.StartTime == nil {
		startTime := time.Now().Add(-7 * 24 * time.Hour)
		q.StartTime = &startTime
	}
	from := q.StartTime.Unix()
	for i := 0; i < len(inDuraDate)-1; i++ {
		agg = agg.RangeItem(&elastic.SplitItem{
			Key:  inDuraDate[i].Format("2006-01-02"),
			From: from,
			To:   inDuraDate[i+1].Unix(),
		})
		from = inDuraDate[i+1].Unix()
	}
	agg = agg.SubAggregation("avg_score", elastic.NewAvgAggregation("score"))
	res, err := elastic.NewEsClient().Size(0).Query(query).Aggregation("train_at_range", agg).Search()
	if err != nil {
		return err
	}
	q.SetResult(res)
	return nil
}

func (q *Query) Total(ctx *gin.Context) (*model.RespTotal, error) {
	if err := q.EsSearchTotal(); err != nil {
		tools.Resp400(ctx, err.Error())
		return nil, err
	}
	respTotal := &model.RespTotal{Type: q.Constitute.Type}
	for _, item := range q.esResp.Aggregation.TrainAtRange.Buckets {
		respTotal.Point = append(respTotal.Point, model.Total{
			Name:     item.Key.(string),
			Count:    item.DocCount,
			AvgScore: item.AvgScore.Value,
		})
	}
	return respTotal, nil
}

func (q *Query) Count(ctx *gin.Context) (*model.Count, error) {
	if err := q.EsSearchCount(); err != nil {
		tools.Resp400(ctx, err.Error())
		return nil, err
	}
	log.Logger.Warn("count debug 1")
	count := &model.Count{}
	log.Logger.Warn("count debug 1.5")
	count.Type = q.Constitute.Type
	log.Logger.Warn("count debug 2")
	count.Total = q.esResp.Hits.Total.Value
	log.Logger.Warn("count debug 3")
	constituteBuckets := q.esResp.Aggregation.ConstituteCondition.Buckets
	if len(constituteBuckets) > 0 {
		count.Constitute.Max = constituteBuckets[0].DocCount
		count.Constitute.MaxName = constituteBuckets[0].Key.(string)
		count.Constitute.Min = constituteBuckets[len(constituteBuckets)-1].DocCount
		count.Constitute.MinName = constituteBuckets[len(constituteBuckets)-1].Key.(string)
	}
	careerBuckets := q.esResp.Aggregation.CareerCondition.Buckets
	if len(careerBuckets) > 0 {
		count.Career.Max = careerBuckets[0].DocCount
		count.Career.MaxName = careerBuckets[0].Key.(string)
		count.Career.Min = careerBuckets[len(careerBuckets)-1].DocCount
		count.Career.MinName = careerBuckets[len(careerBuckets)-1].Key.(string)
	}
	return count, nil
}

func (q *Query) Condition(ctx *gin.Context) error {
	if err := q.EsSearchCount(); err != nil {
		tools.Resp400(ctx, err.Error())
		return err
	}
	if q.MemberInfo == nil {
		q.MemberInfo = make([]model.CountInfo, 0)
	}
	if q.CareerInfo == nil {
		q.CareerInfo = make([]model.CountInfo, 0)
	}
	for _, career := range q.esResp.Aggregation.CareerCondition.Buckets {
		q.CareerInfo = append(q.CareerInfo, model.CountInfo{
			Name:     career.Key.(string),
			Count:    career.DocCount,
			AvgScore: career.AvgScore.Value,
		})
	}
	for _, constitute := range q.esResp.Aggregation.ConstituteCondition.Buckets {
		q.MemberInfo = append(q.MemberInfo, model.CountInfo{
			Name:     constitute.Key.(string),
			Count:    constitute.DocCount,
			AvgScore: constitute.AvgScore.Value,
		})
	}

	return nil
}

func (q *Query) InDuraDate() []time.Time {
	year, month, day := q.StartTime.Date()
	startDate := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
	allDate := make([]time.Time, 0)
	allDate = append(allDate, startDate)

	nextDate := startDate.Add(24 * time.Hour)
	if q.EndTime == nil {
		endTime := time.Now()
		q.EndTime = &endTime
	}
	for nextDate.Before(*q.EndTime) {
		allDate = append(allDate, nextDate)
		nextDate = nextDate.Add(24 * time.Hour)
	}
	allDate = append(allDate, nextDate.Add(24*time.Hour))
	return allDate
}

func (q *Query) EsDataExport(ctx *gin.Context) (string, error) {
	var outs [][]interface{}
	for _, s := range common.TrainLogSummarySheet {
		itemresp, err := q.EsSearchLabel(common.TrainLogSummaryMap[s], true)
		if err != nil {
			return "", err
		}
		var out []interface{}
		for i, item := range itemresp {
			p_ := fmt.Sprintf("%.2f", item.Percent)
			out = append(out, TrainAbilityExport{
				number:  int64(i + 1),
				Label:   item.Label,
				Count:   item.Count,
				Percent: p_,
			})
		}
		outs = append(outs, out)
	}
	dir, _ := os.Getwd()
	//os.Setenv("env", "local")
	//if os.Getenv("env") == "local" {
	//	dir = "."
	//}
	fileName := tools.Md5DataString(time.Now().String()) + ".xlsx"
	localPath := dir + "/trainAbilitySumary_" + fileName
	if _, err := tools.SaveSheetsExcle(localPath, common.TrainLogSummarySheet, common.TrainLogSummaryHeader, outs); err != nil {
		log.Logger.InfoF(ctx, "文件保存失败"+fileName)
		// 记录日志
		go func() {
			defer tools.RecoverGoroutine()
			new(tables.OptionLog).InsertOptionLog(ctx, int64(len(outs)), err)
		}()
		return "", err
	} else {
		filePath := common.ExportFilePath + fileName
		sdk.FilePut(localPath, variable.Config.UfsInfo.BucketPrefix+filePath, sdk.XlsxMetaType)
		os.Remove(localPath)
		// 记录日志
		go func() {
			defer tools.RecoverGoroutine()
			new(tables.OptionLog).InsertOptionLog(ctx, int64(len(outs)), err)
		}()
		return variable.Config.UfsInfo.UfsUrl + variable.Config.UfsInfo.BucketPrefix + filePath, nil
	}
}

// 个人数据导出
func (q *Query) EsDataPersonalExport(ctx *gin.Context) (string, error) {
	// 根据用户id导出训练记录
	trainList, err := train_service.QueryTrainLogByUserId(ctx, q.UserId)
	if err != nil {
		return "", fmt.Errorf("导出错误，请重试")
	}
	if len(trainList) == 0 {
		return "", fmt.Errorf("数据为空，不能导出数据")
	}

	var outs [][][]interface{}

	var tl [][]interface{}
	for i := 0; i < len(trainList); i++ {
		var out []interface{}
		out = append(out, trainList[i])
		tl = append(tl, out)
	}
	outs = append(outs, tl)

	for _, s := range common.TrainLogSummarySheet {
		itemresp, err := q.EsSearchLabel(common.TrainLogSummaryMap[s], true)
		if err != nil {
			return "", err
		}
		var out [][]interface{}
		for i, item := range itemresp {
			var tae []interface{}
			p_ := fmt.Sprintf("%.2f", item.Percent)
			tae = append(tae, TrainAbilityExport{
				number:  int64(i + 1),
				Label:   item.Label,
				Count:   item.Count,
				Percent: p_,
			})
			out = append(out, tae)
		}
		outs = append(outs, out)
	}
	dir, _ := os.Getwd()
	//os.Setenv("env", "local")
	//if os.Getenv("env") == "local" {
	//	dir = "."
	//}
	fileName := tools.Md5DataString(time.Now().String()) + ".xlsx"
	localPath := dir + "/trainAbilitySumary_" + fileName

	var header = make(map[int][]string, len(common.TrainLogSummarySheet)+1)
	for i := 0; i < len(common.TrainLogSummarySheet)+1; i++ {
		if i == 0 {
			header[i] = common.TrainLogDetailHeader
			continue
		}
		header[i] = common.TrainLogPersonHeader
	}

	if _, err := tools.SaveMultSheetsExcle(localPath, common.TrainLogPersonalSheet, header, outs); err != nil {
		log.Logger.InfoF(ctx, "文件保存失败"+fileName)
		// 记录日志
		go func() {
			defer tools.RecoverGoroutine()
			new(tables.OptionLog).InsertOptionLog(ctx, int64(len(outs)), err)
		}()
		return "", err
	} else {
		filePath := common.ExportFilePath + fileName
		sdk.FilePut(localPath, variable.Config.UfsInfo.BucketPrefix+filePath, sdk.XlsxMetaType)
		os.Remove(localPath)
		// 记录日志
		go func() {
			defer tools.RecoverGoroutine()
			new(tables.OptionLog).InsertOptionLog(ctx, int64(len(outs)), err)
		}()
		return variable.Config.UfsInfo.UfsUrl + variable.Config.UfsInfo.BucketPrefix + filePath, nil
	}
}

type EsAggregationRes struct {
	Hits        *Hit             `json:"hits"`
	Aggregation *AggregationResp `json:"aggregations"`
}

type AggregationResp struct {
	CareerCondition     *BucketsCondition `json:"career_condition"`
	ConstituteCondition *BucketsCondition `json:"constitute_condition"`
	TrainAtRange        *TrainAtRange     `json:"train_at_range"`
	SessionIdLi         *SessionIdLi      `json:"session_id_li"`
	TotalUserCount      *Value            `json:"total_user_count"`
	LabelAggs           *LabelAgg         `json:"label_aggs"`
	ExpressAvg          *Value            `json:"expressive_avg"`
	EmphasisAvg         *Value            `json:"emphasis_avg"`
	LogicAvg            *Value            `json:"logic_avg"`
	DistinctUserCount   *Value            `json:"distinct_user_count"`
	ExceedPercent       *BucketsCondition `json:"exceed_percent"`
}

type Value struct {
	Value interface{} `json:"value"`
}

type TrainAtRange struct {
	Buckets []*Bucket `json:"buckets"`
}

type BucketsCondition struct {
	SumOtherDocCount uint      `json:"sum_other_doc_count"`
	Buckets          []*Bucket `json:"buckets"`
}

type Bucket struct {
	Key               interface{} `json:"key"`
	DocCount          int64       `json:"doc_count"`
	DistinctUserCount struct {
		Value int `json:"value"`
	} `json:"distinct_user_count"`
	AvgScore struct {
		Value float64 `json:"value"`
	} `json:"avg_score"`
}

type Hit struct {
	Total struct {
		Value int64 `json:"value"`
	} `json:"total"`
}

type SessionIdLi BucketsCondition
type LabelAgg BucketsCondition

type InterviewState struct {
	ExpressiveAvg float64 `json:"expressive_avg"`
	EmphasisAvg   float64 `json:"emphasis_avg"`
	LogicAvg      float64 `json:"logic_avg"`
}

type TrainAbilityExport struct {
	number  int64
	Label   string `json:"label"`
	Count   int    `json:"count"`
	Percent string `json:"percent"`
}
